package com.platform.qujia.asp;

import com.platform.qujia.anno.SimpleRepeatedReqLimit;
import com.platform.qujia.utils.CodeMsg;
import com.platform.qujia.utils.exception.CustomException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
@Order(1) //
public class SimpleRepeatedReqLimitAsp {

    private static final Logger log = LoggerFactory.getLogger(SimpleRepeatedReqLimit.class);

    private static final ThreadLocal<String> KEY_THREAD_LOCAL = new ThreadLocal<>();
    private static final String KEY_PREFIX = "repeatedReq:";
    private final Map<String, Expression> expressionMap = new ConcurrentHashMap<>();
    private final Map<String, Expression> conditionMap = new ConcurrentHashMap<>();
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Pointcut(value = "@annotation(com.platform.qujia.anno.SimpleRepeatedReqLimit)")
    private void anyMethod(){}


    @Before("anyMethod() && @annotation(repeatedReqLimit)")
    public void before(JoinPoint joinPoint, SimpleRepeatedReqLimit repeatedReqLimit){
        String signature = getSignature(joinPoint);
        if (isMeetCondition(signature,joinPoint,repeatedReqLimit)) {
            String redisKey = getRedisKey(signature,joinPoint, repeatedReqLimit);
            if (isRepeatedReq(redisKey, repeatedReqLimit.expire(), repeatedReqLimit.timeUnit())) {
                log.error("重复请求,method:[{}],redisKey:[{}]",signature,redisKey);
                throw new CustomException(CodeMsg.REPEATED_REQUEST);
            }
            KEY_THREAD_LOCAL.set(redisKey);
        }
    }

    @After("anyMethod()")
    public void after(){
        String redisKey = KEY_THREAD_LOCAL.get();
        KEY_THREAD_LOCAL.remove();
        releaseKey(redisKey);
    }

    private Expression getExpression(String signature, SimpleRepeatedReqLimit repeatedReqLimit){
        String keyExpress = repeatedReqLimit.express();
        if (StringUtils.isEmpty(keyExpress)) {
            return null;
        }
        Expression expression = expressionMap.get(signature);
        if (expression != null) {
            return expression;
        }
        SpelExpressionParser parser = new SpelExpressionParser();
        expression = parser.parseExpression(keyExpress);
        expressionMap.put(signature,expression);
        return expression;
    }

    private String getRedisKey(String signature,JoinPoint joinPoint, SimpleRepeatedReqLimit repeatedReqLimit){
        String keyPrefix = StringUtils.isBlank(repeatedReqLimit.name()) ? "" : repeatedReqLimit.name();
        Expression expression = getExpression(signature, repeatedReqLimit);
        if (expression == null) {
            return KEY_PREFIX+keyPrefix;
        }
        Object[] args = joinPoint.getArgs();
        Object value = expression.getValue(args);
        String key = String.valueOf(value);
        return KEY_PREFIX+keyPrefix+key;
    }

    private boolean isMeetCondition(String signature,JoinPoint joinPoint, SimpleRepeatedReqLimit repeatedReqLimit){
        Expression conditionExpression = getConditionExpression(signature,repeatedReqLimit);
        if (conditionExpression == null) {
            return true;
        }
        Object[] args = joinPoint.getArgs();
        Boolean value = conditionExpression.getValue(args, Boolean.class);
        return Boolean.TRUE.equals(value);
    }

    private Expression getConditionExpression(String signature, SimpleRepeatedReqLimit repeatedReqLimit){
        if (StringUtils.isEmpty(repeatedReqLimit.condition())) {
            return null;
        }
        Expression expression = conditionMap.get(signature);
        if (expression != null) {
            return expression;
        }
        SpelExpressionParser parser = new SpelExpressionParser();
        expression = parser.parseExpression(repeatedReqLimit.condition());
        conditionMap.put(signature,expression);
        return expression;
    }

    private boolean isRepeatedReq(String key, int time, TimeUnit timeUnit){
        return !Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, StringUtils.EMPTY, time, timeUnit));
    }

    private void releaseKey(String key){
        if (StringUtils.isEmpty(key)) {
            return;
        }
        redisTemplate.delete(key);
    }

    private String getSignature(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        return signature.getDeclaringTypeName()+"."+signature.getName();
    }

}
